package com.example.waitdemo.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * @Description
 * @ClassName jiangAspectj
 * @Author jiang_11445266119198
 * @Date 2022/7/12 12:16
 */
@Aspect     // 切面
@Component  // 表示这是一个bean,由Spring进行管理 或者说输入spring
public class jiangAspectj {

    private Logger logger = LoggerFactory.getLogger(jiangAspectj.class);

    // 配置织入点
    @Pointcut("@annotation(com.example.waitdemo.config.jiang)")
    public void selectOneAspect() {
    }

    // 前置通知（Before） 在执行目标方法之前执行
    @Before("selectOneAspect()")
    public void beforeSelect(JoinPoint point) throws Throwable {
        System.out.println("before");
        // 获取 @WebLog 注解的描述信息
        String methodDescription = getAspectLogDescription(point);
        logger.info("【注解：Before】注解信息 : " + methodDescription);

        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        System.out.println("String = " +  getParameter(method, point.getArgs()));
        System.out.println("Target = " +  point.getTarget());
        System.out.println("Kind = " +  point.getKind());
        System.out.println("String = " +  point.getThis());
        Object[] args = point.getArgs();
        System.out.println("args = " + args);
        for (Object item : args) {
            System.out.println(item);
        }
        System.out.println("before");
        // 接收到请求，记录请求内容
        logger.info("【注解：Before】------------------切面  before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.info("【注解：Before】浏览器输入的网址=URL : " + request.getRequestURL().toString());
        logger.info("【注解：Before】HTTP_METHOD : " + request.getMethod());
        logger.info("【注解：Before】IP : " + request.getRemoteAddr());
        logger.info("【注解：Before】执行的业务方法名=CLASS_METHOD : " + point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName());
        logger.info("【注解：Before】业务方法获得的参数=ARGS : " + Arrays.toString(point.getArgs()));
    }

    // 后置通知（After）   在目标方法之后执行（不管目标方法是否执行成功都会执行）
    /*@After("selectOneAspect()")
    public void logAfter(JoinPoint point){
        System.out.println("after");
    }*/

    // 正常后置通知（AfterReturning） 目标方法正常执行结束后执行，result是目标方法返回的结果
    @AfterReturning(pointcut = "selectOneAspect()", returning = "result")
    public void logAfterReturning(JoinPoint point, Object result) {
        // 1、从request里面获取token
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String token = request.getHeader("token");

        System.out.println("result = " + result);
        System.out.println("logAfterReturning");
    }


    // 异常后置通知（AfterThrowing）   目标方法异常后执行
    @AfterThrowing("selectOneAspect()")
    public void logAfterThrowing(JoinPoint point) {
        System.out.println("logAfterThrowing");
    }

    // 环绕通知（Around） 目标方法执行前后都执行
    /*@Around("selectOneAspect()")
    public Object logAround(ProceedingJoinPoint point) throws Throwable {
        System.out.println("目标方法执行前...");
        Object proceed = point.proceed();   // 去执行目标方法， proceed 是目标方法的返回值。
        System.out.println("目标方法执行后...");
        // -----------------------------------------------------


         //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        Object[] objs = point.getArgs();
        WebLog webLog = new WebLog();
        Object result = point.proceed();//返回的结果，这是一个进入方法和退出方法的一个分界
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(request.getRemoteUser());
        webLog.setMethod(request.getMethod());
        webLog.setParameter(getParameter(method, point.getArgs()));
        webLog.setResult(JSONUtil.parse(result));
        webLog.setSpendTime((int) (endTime - startTime));
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(request.getRequestURL().toString());
        controlLog.info("RequestAndResponse {}", JSONObject.toJSONString(webLog));
        //必须有这个返回值。可以这样理解，Around方法之后，不再是被织入的函数返回值，而是Around函数返回值
        return result;
        return proceed;
    }*/

/*我们看到每一个通知方法里面都有JoinPoint参数，我们可以从里面获取数据，比如获取请求的参数

Object[] args = joinPoint.getArgs();
for (Object item : args){
    System.out.println(item);
}
*/

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            } else {
                argList.add(args[i]);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }


    /**
     * 获取切面注解的描述
     *
     * @param joinPoint 切点
     * @return 描述信息
     * @throws Exception
     */
    public String getAspectLogDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        StringBuilder description = new StringBuilder("");
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description.append(method.getAnnotation(jiang.class).description());
                    break;
                }
            }
        }
        return description.toString();
    }

    /* 、、其他、、*/

    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        System.out.println("环绕通知的目标方法名：" + proceedingJoinPoint.getSignature().getName());
        //修改传入参数
        processInputArg(proceedingJoinPoint.getArgs());
        try {//obj之前可以写目标方法执行前的逻辑
            //调用执行目标方法
            Object obj = proceedingJoinPoint.proceed();
            //处理返回值
//            processOutPutObj(obj);
            return obj;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    /**
     * 处理返回对象
     */
    private void processOutPutObj(Object obj) {
        System.out.println("OBJ 原本为：" + obj.toString());
        //此处也可以直接修改返回的对象
    }

    /**
     * 处理输入参数
     *
     * @param args 入参列表
     */
    private void processInputArg(Object[] args) {
        for (Object arg : args) {
            Map m = (Map) arg;
            m.put("ywy", "ywy");
//            if (arg instanceof ParamVO) {
//                ParamVO paramVO = (ParamVO) arg;
//                paramVO.setInputParam("654321");
//            }
        }
    }


}
